﻿// Copyright 2006 - Ricardo Stuven (rstuven@gmail.com)
//
// This file is part of MsSqlSpatial.
// MsSqlSpatial is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
// 
// MsSqlSpatial is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.

// You should have received a copy of the GNU Lesser General Public License
// along with MsSqlSpatial; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

using System.Collections.Generic;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using GeoAPI.CoordinateSystems;
using GeoAPI.CoordinateSystems.Transformations;
using GeoAPI.Geometries;
using GisSharpBlog.NetTopologySuite.CoordinateSystems.Transformations;
using Microsoft.SqlServer.Server;
using MsSqlSpatial;
using SharpMap.CoordinateSystems;
using SharpMap.CoordinateSystems.Transformations;
//using GisSharpBlog.NetTopologySuite.CoordinateSystems;
//using GisSharpBlog.NetTopologySuite.CoordinateSystems.Transformations;


public partial class UserDefinedFunctions
{
    private static readonly CoordinateSystemFactory coordinateSystemFactory = new CoordinateSystemFactory();

    private static readonly CoordinateTransformationFactory coordinateTransformationFactory =
        new CoordinateTransformationFactory();

    private static readonly Dictionary<TransformCacheKey, IMathTransform> transformCache =
        new Dictionary<TransformCacheKey, IMathTransform>(new TransformCacheKey());

    [SqlFunction(IsDeterministic = false, DataAccess = DataAccessKind.Read)]
    public static SqlBytes Transform(SqlBytes ewkb, SqlInt32 target_srid)
    {
        IGeometry geometry = ConvertGeometry.ToGeometry(ewkb);
        if (geometry != null)
        {
            int sourceSRID = geometry.SRID;
            int targetSRID = target_srid.Value;

            // Return the original geometry if SRIDs are equal
            if (sourceSRID == targetSRID)
                return ewkb;
            else
            {
                // Look up coordinate system transformation object in cache
                var cacheKey = new TransformCacheKey(sourceSRID, targetSRID);
                IMathTransform mathTransform;
                if (!transformCache.TryGetValue(cacheKey, out mathTransform))
                {
                    if (!SpatialRefSysTable.IsValidSRID(targetSRID))
                        throw new InvalidSridException(targetSRID);

                    // Create coordinate system transformation object and put it in cache
                    string sourceSRWKT = SpatialRefSysTable.GetSRText(sourceSRID);
                    string targetSRWKT = SpatialRefSysTable.GetSRText(targetSRID);
                    ICoordinateSystem sourceCS = coordinateSystemFactory.CreateFromWkt(sourceSRWKT);
                    ICoordinateSystem targetCS = coordinateSystemFactory.CreateFromWkt(targetSRWKT);
                    ICoordinateTransformation transform =
                        coordinateTransformationFactory.CreateFromCoordinateSystems(sourceCS, targetCS);
                    mathTransform = transform.MathTransform;
                    transformCache[cacheKey] = mathTransform;
                }
                // Transform source geometry's coordinate system
                IGeometry sourceGeometry = geometry;
                IGeometry targetGeometry = GeometryTransform.TransformGeometry(sourceGeometry, mathTransform);
                return ConvertGeometry.ToSqlBytes(targetGeometry, targetSRID);
            }
        }
        return SqlBytes.Null;
    }

    // This trigger is registered in SpatialRefSysTable::ExecuteCreate
    public static void RefreshTransformCacheTrigger()
    {
        SqlTriggerContext triggerContext = SqlContext.TriggerContext;
        using (SqlConnection connection = DatabaseHelper.ContextConnection)
        {
            connection.Open();
            using (SqlCommand command = connection.CreateCommand())
            {
                // Get SRID of updated SRTEXT or deleted record.
                int srid = -1;
                if (triggerContext.TriggerAction == TriggerAction.Update
                    && triggerContext.IsUpdatedColumn(3 /* SRTEXT */))
                {
                    DatabaseHelper.BuildCommand(command, "SELECT srid FROM inserted");
                    try
                    {
                        srid = (int) command.ExecuteScalar();
                    }
                    catch
                    {
                    }
                }
                else if (triggerContext.TriggerAction == TriggerAction.Delete)
                {
                    DatabaseHelper.BuildCommand(command, "SELECT srid FROM deleted");
                    try
                    {
                        srid = (int) command.ExecuteScalar();
                    }
                    catch
                    {
                    }
                }
#if DEBUG
				SqlContext.Pipe.Send(string.Format("Trigger on SRID: {0}", srid));
#endif
                if (srid != -1)
                {
                    // Build list of cache key items containing SRID
                    var removeKeys = new List<TransformCacheKey>();
                    foreach (TransformCacheKey cacheKey in transformCache.Keys)
                    {
                        if (cacheKey.part1 == srid || cacheKey.part2 == srid)
                        {
                            removeKeys.Add(cacheKey);
                        }
                    }
                    // Remove by found cache keys
                    foreach (TransformCacheKey cacheKey in removeKeys)
                    {
                        transformCache.Remove(cacheKey);
#if DEBUG
						SqlContext.Pipe.Send(string.Format("{0} removed from transform cache.", cacheKey));
#endif
                    }
                }
            }
        }
    }

#if DEBUG
	[SqlProcedure]
	public static void TransformCache_List()
	{
		SqlContext.Pipe.Send(string.Format("{0} items in cache.", transformCache.Count));
		foreach (KeyValuePair<TransformCacheKey, IMathTransform> item in transformCache)
			SqlContext.Pipe.Send(string.Format("\t{0}", item.Key));
	}

	[SqlProcedure]
	public static void TransformCache_Clear()
	{
		transformCache.Clear();
		SqlContext.Pipe.Send("Cache cleared.");
	}

	[SqlProcedure]
	public static void TransformCache_Get(int sourceSRID, int targetSRID)
	{
		TransformCacheKey key = new TransformCacheKey(sourceSRID, targetSRID);
		IMathTransform mathTransform;
		if (transformCache.TryGetValue(key, out mathTransform))
			SqlContext.Pipe.Send(string.Format("{0} exists in cache.", key));
		else
			SqlContext.Pipe.Send(string.Format("{0} doesn't exist in cache.", key));
	}

	[SqlProcedure]
	public static void TransformCache_Remove(int sourceSRID, int targetSRID)
	{
		TransformCacheKey key = new TransformCacheKey(sourceSRID, targetSRID);
		if (transformCache.Remove(key))
			SqlContext.Pipe.Send(string.Format("{0} removed from cache.", key));
		else
			SqlContext.Pipe.Send(string.Format("{0} doesn't exist in cache.", key));
	}
#endif

    #region Nested type: CompositeKey

    private class CompositeKey<TPart1, TPart2> : IEqualityComparer<CompositeKey<TPart1, TPart2>>
    {
        public readonly TPart1 part1;
        public readonly TPart2 part2;

        public CompositeKey()
        {
        }

        public CompositeKey(TPart1 part1, TPart2 part2)
        {
            this.part1 = part1;
            this.part2 = part2;
        }

        #region IEqualityComparer<CompositeKey<TPart1,TPart2>> Members

        public bool Equals(CompositeKey<TPart1, TPart2> x, CompositeKey<TPart1, TPart2> y)
        {
            return x.part1.Equals(y.part1) && x.part2.Equals(y.part2);
        }

        public int GetHashCode(CompositeKey<TPart1, TPart2> obj)
        {
            return obj.part1.GetHashCode() ^ obj.part2.GetHashCode();
        }

        #endregion

#if DEBUG
		public override string ToString()
		{
			return string.Format("({0}, {1})", this.part1, this.part2);
		}
#endif
    }

    #endregion

    #region Nested type: TransformCacheKey

    private class TransformCacheKey : CompositeKey<int, int>, IEqualityComparer<TransformCacheKey>
    {
        public TransformCacheKey()
        {
        }

        public TransformCacheKey(int sourceSRID, int targetSRID) : base(sourceSRID, targetSRID)
        {
        }

        #region IEqualityComparer<TransformCacheKey> Members

        public bool Equals(TransformCacheKey x, TransformCacheKey y)
        {
            return base.Equals(x, y);
        }

        public int GetHashCode(TransformCacheKey obj)
        {
            return base.GetHashCode(obj);
        }

        #endregion
    }

    #endregion
} ;